﻿using System;
using System.Web;
using System.Threading;
using System.IO;

using Fuse8.DomainFramework.Common;

namespace Fuse8.ContentManagementFramework.Web.Caching
{
    public class CmsOutputCacheTracker
    {
        #region Fields

        private static object _syncRoot = new object();

        private CmsResponseFilter _capturingFilter;
        private HttpResponseBase _capturingResponse;
        private ManualResetEvent _capturingEvent;
        private bool _lookedInCache;
        private bool _loadedCachedResponse;
        private CmsCacheResponseItem _cacheResponseItem;

        //settings
        private string _requestUrl;
        private bool _useGZip;
        private string _cacheDirectoryLocation;
        private bool _serveFromMemory;
        private TimeSpan _capturingWaitTime;

        #endregion

        #region Constructor

        internal CmsOutputCacheTracker(string requestUrl, string encodingTypes, string cacheDirectoryLocation, bool serveFromMemory, TimeSpan capturingWaitTime)
        {
            _requestUrl = requestUrl;
            _useGZip = (encodingTypes ?? string.Empty).ToLower().Contains("gzip");
            _cacheDirectoryLocation = cacheDirectoryLocation;
            _serveFromMemory = serveFromMemory;
            _capturingWaitTime = capturingWaitTime;

            _capturingEvent = new ManualResetEvent(true);
            _cacheResponseItem = new CmsCacheResponseItem();
        } 

        #endregion

        #region Public methods

        public bool TrySendResponseOrStartResponseCapture(HttpResponseBase httpResponse)
        {
            Guard.ArgumentNotNull(httpResponse, "httpResponse");

            bool result = false;

            _capturingEvent.WaitOne(_capturingWaitTime);

            lock (_syncRoot)
            {
                if (!_lookedInCache)
                {
                    LookForCachedResponseOnDisk();

                    _lookedInCache = true;
                }

                if (!_loadedCachedResponse)
                {
                    if (_capturingResponse == null)
                    {
                        _capturingFilter = new CmsResponseFilter(httpResponse.Filter, GetOutputFileName(), _useGZip);
                        httpResponse.Filter = _capturingFilter;

                        _capturingEvent.Reset();

                        _capturingResponse = httpResponse;
                    }
                }
            }

            if (_cacheResponseItem.CacheResponseBytes != null)
            {
                httpResponse.OutputStream.Write(_cacheResponseItem.CacheResponseBytes, 0, _cacheResponseItem.CacheResponseBytes.Length);

                result = true;
            }
            else if (!string.IsNullOrEmpty(_cacheResponseItem.CacheDataFilename))
            {
                try
                {
                    httpResponse.TransmitFile(_cacheResponseItem.CacheDataFilename);

                    result = true;
                }
                catch
                {
                    InvalidateCachedResponse();
                }
            }

            if (result && _useGZip)
            {
                httpResponse.AppendHeader("Content-Encoding", "gzip");
            }

            return result;
        }

        public void FinishCaptureAndSaveResponse(HttpResponseBase httpResponse)
        {
            Guard.ArgumentNotNull(httpResponse, "httpResponse");

            FinishOrCancelCapture(httpResponse, false);
        }

        public void CancelCapture(HttpResponseBase httpResponse)
        {
            Guard.ArgumentNotNull(httpResponse, "httpResponse");

            if (_capturingResponse != null)
            {
                FinishOrCancelCapture(httpResponse, true);
            }
        } 

        #endregion

        #region Private methods

        private void FinishOrCancelCapture(HttpResponseBase response, bool cancel)
        {
            if (_capturingFilter != null)
            {
                lock (_syncRoot)
                {
                    try
                    {
                        _capturingFilter.StopFiltering(cancel);

                        if (!cancel)
                        {
                            _cacheResponseItem.CacheDataFilename = _capturingFilter.CaptureFilename;

                            if (_serveFromMemory)
                            {
                                _cacheResponseItem.CacheResponseBytes = File.ReadAllBytes(_cacheResponseItem.CacheDataFilename);
                            }

                            _loadedCachedResponse = true;
                        }
                    }
                    finally
                    {
                        _capturingFilter = null;
                        _capturingResponse = null;

                        _capturingEvent.Set();
                    }
                }
            }
        }

        private void LookForCachedResponseOnDisk()
        {
            string dataFilename = GetOutputFileName();

            if (File.Exists(dataFilename))
            {
                if (_serveFromMemory)
                {
                    _cacheResponseItem.CacheResponseBytes = File.ReadAllBytes(dataFilename);
                }
                else
                {
                    _cacheResponseItem.CacheDataFilename = dataFilename;
                }

                _loadedCachedResponse = true;
            }
        }

        private void InvalidateCachedResponse()
        {
            CmsOutputCacheManager.ScheduleFileDeletion(GetOutputFileName());

            _loadedCachedResponse = false;
        }

        private string GetOutputFileName()
        {
            string path = _requestUrl.Replace("/", "_");

            if (_useGZip)
            {
                path = string.Format("{0}_gzip", path);
            }

            return Path.Combine(_cacheDirectoryLocation, string.Format("{0}.gzip", path));
        }

        #endregion
    }
}
